using System;
using System.Reflection;
using ValidationFramework.Extensions;
using ValidationFramework.Reflection;

namespace ValidationFramework.Specifications
{
	/// <summary>
	/// A Specification which takes and uses a delegate to test values.
	/// </summary>
#if (!SILVERLIGHT)
    [Serializable]
#endif
	public class SatisfiesDelegateSpec : SpecificationBase
	{
		// --- Public Delegate 
		public delegate bool SpecDelegate(object instanceToValidate);

		public static SpecDelegate ResolveValidateMethod(string typeName, string methodName)
		{
			Guard.ArgumentNotNullOrEmptyString(typeName, "typeName");

			Type validatyionType;
			try
			{
				validatyionType = Type.GetType(typeName, true);
			}

#if (SILVERLIGHT)
        	catch (FileLoadException fileLoadException)
        	{
        		//TODO: remove when this is fixed in silverlight
        		if (fileLoadException.Message.Contains("The requested assembly version conflicts with what is already bound in the app domain or specified in the manifest."))
        		{
					throw new ArgumentException(string.Format("Could not load type {0}. If you are using silverlight please qualify the CustomRuleAttribute 'validationTypeName' with 'Version', 'Culture' and 'PublicKeyToken'.", validationTypeName), fileLoadException);
        		}
        		else
        		{
        			throw new ArgumentException(string.Format("Could not load type {0}.", validationTypeName), fileLoadException);
        		}
        	}
#endif
			catch (Exception exception)
			{
				throw new ArgumentException(string.Format("Could not load type {0}.", typeName), exception);
			}

			return ResolveValidateMethod(validatyionType, methodName);
		}
		public static SpecDelegate ResolveValidateMethod(Type validationType, string methodName)
		{
			Guard.ArgumentNotNull(validationType, "validationType");
			Guard.ArgumentNotNullOrEmptyString(methodName, "methodName");

			// reflect the method info
			var validationMethodInfo = validationType.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (validationMethodInfo == null)
			{
				throw new ArgumentException(string.Format("Could not load the method '{0}' from type '{1}'.", methodName, validationType.ToUserFriendlyString()), "validationMethod");
			}
		
			var validateMethod = Delegate.CreateDelegate(typeof(SpecDelegate), null, validationMethodInfo) as SpecDelegate;
			if (validateMethod == null)
			{
				throw new Exception("Could not resolve delegate.");
			}
			return validateMethod;
		}

		// --- Fields
		// exposed for testing
		internal SpecDelegate _specDelegate;
		private readonly string _interpretation;
		private readonly long _uniqueHash;

		// --- Constructors
		public SatisfiesDelegateSpec(SpecDelegate specDelegate, string interpretation)
		{
			Guard.ArgumentNotNullOrEmptyString(interpretation, "ruleInterpretation");
			_specDelegate = specDelegate;
			this._interpretation = interpretation;
		}
		//public DelegateSpecification(Type validationType, string validationMethod, string interpretation)
		//{
		//    Guard.ArgumentNotNull(validationType, "validationType");
		//    Guard.ArgumentNotNullOrEmptyString(validationMethod, "validationMethod");
		//    Guard.ArgumentNotNullOrEmptyString(interpretation, "ruleInterpretation");

		//    this.interpretation = interpretation;
    
		//    // reflect the method info
		//    var validationMethodInfo = validationType.GetMethod(validationMethod, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		//    if (validationMethodInfo == null)
		//    {
		//        throw new ArgumentException(string.Format("InlineSpecification could not load the validation method '{0}' from type '{1}'.", validationMethod, validationType.ToUserFriendlyString()), "validationMethod");
		//    }
		//    uniqueHash = validationMethodInfo.MethodHandle.Value.ToInt64();

		//    // create the delegate
		//    this.validateMethod = Delegate.CreateDelegate(typeof(ValidateMethod), null, validationMethodInfo) as ValidateMethod;

		//    if (this.validateMethod == null)
		//    {
		//        throw new Exception("Could not create delegate.");
		//    }
		//}

		// --- Methods | Overrides

		public override bool IsEquivalentTo(ISpecification spec)
		{
			var dv = (SatisfiesDelegateSpec)spec;
			return dv._specDelegate.Method.Equals(_specDelegate.Method);
		}

		public override bool IsSatisfiedBy(object target)
		{
			return _specDelegate(target);
		}

		public override string ToString()
		{
			return _interpretation;
		}
	}
}
